package cn.epsilon3.aop;

import cn.epsilon3.annotation.E3Des;
import cn.epsilon3.annotation.E3DesField;
import cn.epsilon3.common.Result;
import cn.epsilon3.constant.DesensitizationFieldEnum;
import cn.epsilon3.page.Page;
import cn.epsilon3.page.PageBean;
import cn.epsilon3.exception.Epsilon3Exception;
import cn.hutool.core.util.StrUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

/**
 * @description 脱敏数据返回处理
 *
 * @author epsilon3
 * @version 1.0
 * @project epsilon3-module-core
 * @date 2023/6/10 00:24:34
 */
@ControllerAdvice
public class E3DesensitizationAop implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.hasMethodAnnotation(E3Des.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            Object data = ((Result<?>) body).getData();
            if (data instanceof Collection) {
                forSetFiled(data);
            }
            if (data instanceof PageBean) {
                PageBean pageBean = (PageBean) data;
                Page page = pageBean.getPageBean();
                List list = page.getList();
                forSetFiled(list);
            }
                desensitizationFiled(data);

        }
        return body;
    }

    /**
     * 查找有@DesensitizationFiled的字段
     *
     * @param obj
     * @param <E>
     * @return
     */
    private static <E> E desensitizationFiled(E obj) {
        if (null == obj) {
            return null;
        }
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            E3DesField e3DesField = field.getAnnotation(E3DesField.class);
            if (null == e3DesField) {
                continue;
            }
            if (!field.getType().equals(String.class)) {
                try {
                    Object obj2 = field.get(obj);
                    if (obj2 instanceof Collection) {
                        ((Collection<E>) obj2).forEach((item) ->{
                            desensitizationFiled(item);
                        });
                    } else {
                        desensitizationFiled(obj2);
                    }
                } catch (Exception e) {
                    throw new Epsilon3Exception("复杂字段脱敏异常");
                }
            } else {
                setDesensitizationFiled(field, obj, e3DesField);
            }
        }
        return obj;
    }

    /**
     * 设置脱敏属性
     *
     * @param field
     * @param obj
     * @param e3DesField
     * @param <E>
     */
    private static <E> void setDesensitizationFiled(Field field,
                                                    Object obj,
                                                    E3DesField e3DesField) {
        try {
            Object fieldValue = field.get(obj);
            String value = fieldValue.toString();
            if (StrUtil.isBlank(value)) {
                field.set(obj, fieldValue);
            }
            int hidePrefixLength = e3DesField.hidePrefixLength();
            int hideSuffixLength = e3DesField.hideSuffixLength();
            int prefixLength = e3DesField.prefixLength();
            int suffixLength = e3DesField.suffixLength();
            DesensitizationFieldEnum type = e3DesField.type();
            String replaceStr = e3DesField.replaceStr();
            if (!StrUtil.equals(DesensitizationFieldEnum.NONE.getType(), type.getType())) {
                switch (type) {
                    case EMAIL:
                        value = value.replaceAll("(^\\w)[^@]*(@.*$)", "$1****$2");
                        break;
                    case PHONE:
                        value = value.replaceAll("(^\\d{3})\\d.*(\\d{4})", "$1****$2");
                        break;
                    case NAME:
                        value = value.replaceAll("(/^(.).+$/)", "$1*");
                        break;
                    case ID_CARD18:
                        value = value.replaceAll("(/^(.{4}).+(.{4})$/)", "$1****$2");
                        break;
                }
            } else {
                if (!((hidePrefixLength != 0 || hideSuffixLength != 0) && prefixLength == 0 && suffixLength == 0)) {
                    String regex = "([\\s\\S]{" + prefixLength + "})([\\s\\S]+)([\\s\\S]{" + suffixLength + "})";
                    String replacement = produceCharacter(value.length() - prefixLength - suffixLength, replaceStr);
                    value = value.replaceAll(regex, "$1" + replacement + "$3");
                }
                if (hidePrefixLength > 0 && value.length() > hidePrefixLength) {
                    value = produceCharacter(hidePrefixLength, replaceStr) + value.substring(hidePrefixLength);
                }
                if (hideSuffixLength > 0 && value.length() > hideSuffixLength) {
                    value = value.substring(0, value.length() - hideSuffixLength) + produceCharacter(hideSuffixLength, replaceStr);
                }
            }
            field.set(obj, value);
        } catch (Exception e) {
            throw new Epsilon3Exception("基本字段脱敏异常");
        }
    }

    private static String produceCharacter(int len, String character) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; ++i) {
            sb.append(character);
        }
        return sb.toString();
    }

    private static void forSetFiled(Object data) {
        ((Collection<?>) data).forEach((item) -> {
            desensitizationFiled(item);
        });
    }
}
